#ifndef SWEGL_RENDERER_H
#define SWEGL_RENDERER_H

#include "swegl/common.h"
#include "swegl/utils/fixed_array.h"
#include "swegl/viewport.h"
#include "swegl/math.h"
#include "swegl/vertex_buffer.h"
#include "swegl/index_buffer.h"
#include "swegl/texture.h"
#include "swegl/texture_unit.h"
#include "swegl/texture_stage.h"

#include "swegl/vertex_format.h"
#include "swegl/renderer_stats.h"
#include "swegl/render_vertex.h"

#include <vector>

namespace swegl
{

class Primitive
{
public:
	enum Type
	{
		TRIANGLE_LIST,
		TRIANGLE_STRIP,
		LINE_LIST,
		LINE_STRIP,
		POINT_LIST,
	};
};

class DepthFunc
{
public:
	enum Type
	{
		Never,
		Less,
		Equal,
		LessEqual,
		Greater,
		NotEqual,
		GreaterEqual,
		Always,
	};
};

class CullMode
{
public:
	enum Type
	{
		None,
		Front,
		Back,
	};
};

class FillMode
{
public:
	enum Type
	{
		Wireframe,
		Solid,
	};
};

namespace ClearFlags
{
	const uint Target = 1;
	const uint ZBuffer = 2;
	const uint Stencil = 4;
};

class Color
{
public:
	Color();
	Color(uint32 argb);
	Color(float a, float r, float g, float b);
	Color &clamp();
	uint32 asUint32() const;

	float a, r, g, b;
};

inline Color::Color() { }
inline Color::Color(uint32 argb)
{
	a = static_cast<float>((argb & 0xff000000) >> 24) / 255;
	r = static_cast<float>((argb & 0x00ff0000) >> 16) / 255;
	g = static_cast<float>((argb & 0x0000ff00) >> 8) / 255;
	b = static_cast<float>((argb & 0x000000ff) >> 0) / 255;
}
inline Color::Color(float a, float r, float g, float b) : a(a), r(r), g(g), b(b) { }

inline Color &Color::clamp()
{
	a = Clamp(a, 0.0f, 1.0f);
	r = Clamp(r, 0.0f, 1.0f);
	g = Clamp(g, 0.0f, 1.0f);
	b = Clamp(b, 0.0f, 1.0f);
	return *this;
}

inline uint32 Color::asUint32() const
{
	return (static_cast<uint32>(a * 255) << 24) | (static_cast<uint32>(r * 255) << 16) | (static_cast<uint32>(g * 255) << 8) | static_cast<uint32>(b * 255);
}

class Gradient
{
public:
	float reference;
	float x, y;
	float z, w;
};

class Rasterizer
{
public:
	Rasterizer();
	~Rasterizer();
	void drawTriangle(const RenderVertex *v0, const RenderVertex *v1, const RenderVertex *v2);

	RenderVertexFormat vertexFormat;
	Surface *m_colorBuffer;
	Surface *m_depthBuffer;
	TextureUnit m_textureUnits[8];
	TextureStage m_textureStages[8];
};

struct TriangleInfo
{
	float m_fAx, m_fAy;
	float m_fBx, m_fBy;
	float m_fCx, m_fCy;

	float m_fRefX;
	float m_fRefY;

	Gradient z;

	FixedArray<Gradient, 4 + 4 + 2 * 8> gradients;

	uint mipLevel;

	bool initialize(const RenderVertex *v0, const RenderVertex *v1, const RenderVertex *v2, Rasterizer &rasterizer);
};

enum ClipPlanes
{
	PlaneLeft = 1 << 0,
	PlaneRight = 1 << 1,
	PlaneTop = 1 << 2,
	PlaneBottom = 1 << 3,
	PlaneNear = 1 << 4,
	PlaneFar = 1 << 5,
};

class Renderer
{
public:
	Renderer();
	~Renderer();

	void openWindow(uint width, uint height);
	void closeWindow();
	// set current viewport. Must be inside window bounds...
	void setViewport(const Viewport &viewport);
	// clear back buffer
	void clear(uint flags, uint32 color);//, const Color &color);
	// clear current viewport, slower than clearing backbuffer, try to avoid it!
	void clearViewport(bool depthBuffer, bool colorBuffer, const Color &color);
	// set current texture of a texture unit
	void setTexture(uint unit, Texture *tex);
	// set current vertex buffer
	void setVertexBuffer(VertexBuffer *vertexBuffer);
	// set current index buffer
	void setIndexBuffer(IndexBuffer *indexBuffer);
	// draw 'numPrimitives' primitives of type 'primitiveType' using 'firstVertex' as base index to access the current vertex buffer
	void draw(Primitive::Type primitiveType, uint numPrimitives, uint firstVertex = 0);
	// draw 'numPrimitives' indexed primitives of type 'primitiveType' using 'startIndex' as base index to access the current index buffer
	void drawIndexed(Primitive::Type primitiveType, uint numPrimitives, uint startIndex = 0);

	VertexBuffer *createVertexBuffer(size_t size);
	IndexBuffer *createIndexBuffer(IndexBuffer::IndexType indexType, size_t numIndices);

	TextureStage *textureStage(size_t stage) { return &m_rasterizer.m_textureStages[stage]; }

	// etc...
	void setFillMode(FillMode::Type mode);
	void setFrontCounterClockwise(bool frontCounterClockwise);
	void setDepthFunction(DepthFunc::Type func);
	void setCullMode(CullMode::Type mode);

	void setWorldMatrix(const Matrix4x4 &worldTransform);
	void setViewMatrix(const Matrix4x4 &viewTransform);
	void setProjectionMatrix(const Matrix4x4 &projectionTransform);

	void setVertexFormat(const InputVertexFormat &format);

	void present();

	RendererStats &stats();

protected:
	RenderVertexArray m_transformedVertexArray;
	typedef std::vector<uint32> TransformedIndexArray;
	TransformedIndexArray m_transformedIndexArray;

	float znear, zfar;
	uint8 computeClipFlags(const RenderVertex *vertex);
	void intersect(RenderVertex *v, const RenderVertex *v0, const RenderVertex *v1, float t);
	
	void transform(const void *vertices, size_t numVertices, size_t firstVertex);
	void clip(size_t i0, size_t i1, size_t i2);

	//typedef FixedArray<uint, 50> IndexArray;
	typedef std::vector<uint> IndexArray;
	// todo: output temporary new vertices in an additional array to insert only the vertices really needed...
	void clipTriangle(const IndexArray &originalVertices, IndexArray &clippedVertices, RenderVertexArray &vertexArray);
	void clipTriangleX(const IndexArray &originalVertices, IndexArray &clippedVertices, const ClipPlanes clipPlane, const float clipValue, RenderVertexArray &vertexArray);
	void clipTriangleY(const IndexArray &originalVertices, IndexArray &clippedVertices, const ClipPlanes clipPlane, const float clipValue, RenderVertexArray &vertexArray);
	void clipTriangleZ(const IndexArray &originalVertices, IndexArray &clippedVertices, const ClipPlanes clipPlane, const float clipValue, RenderVertexArray &vertexArray);

public:
	RendererStats m_stats;
	Viewport m_viewport;
	ClippingViewport m_clippingViewport;
	
	Texture *m_nullTexture;
	VertexBuffer *m_vertexBuffer;
	IndexBuffer *m_indexBuffer;
	Matrix4x4 m_worldMatrix;
	Matrix4x4 m_viewMatrix;
	Matrix4x4 m_projectionMatrix;
	Matrix4x4 m_projViewportMatrix;
	Matrix4x4 m_viewportScaleMatrix;

	InputVertexFormat m_inputVertexFormat;
	Rasterizer m_rasterizer;
};

inline RendererStats &Renderer::stats()
{
	return m_stats;
}

}

#endif 
